using System;
using System.Collections.Generic;
using System.Text;
using SqlTools.SchemaReader;
using System.Xml;
using SqlTools.CodeGenerator.CSharp;
using SqlTools.CodeGenerator.SQL;
using System.ComponentModel;

namespace SqlTools.CodeGenerator.Data
{
	public class Project
	{
		protected Database _database;

		protected string _defaultDataNamespace;
		protected string _defaultBusinessNamespace;
		protected string _defaultDataDirectory;
		protected string _defaultBusinessDirectory;
		protected string _defaultSQLDirectory;
		protected string _rootDirectory;
		protected string _connectionString;
		protected string _defaultConnectionStringName;
		protected string _storedProcedurePrefix;
		protected bool _autoRunSqlScripts;
		protected bool _useGeneratedPartialClassFormat;
		protected List<string> _tablePrefixes;
		protected TableCollection _tables;

		protected bool _dirty;

		protected string _filename;

		public Project()
		{
			_database = null;

			_defaultDataNamespace = "";
			_defaultBusinessNamespace = "";
			_defaultDataDirectory = "";
			_defaultBusinessDirectory = "";
			_defaultSQLDirectory = "";
			_rootDirectory = "";
			_connectionString = "";
			_defaultConnectionStringName = "";
			_storedProcedurePrefix = "";
			_autoRunSqlScripts = false;
			_useGeneratedPartialClassFormat = false;
			_tablePrefixes = new List<string>();
			_tables = new TableCollection();

			_dirty = true;
			_filename = "";
		}

		public Project(Project project)
		{
			_database = null;

			_defaultDataNamespace = project.DefaultDataNamespace;
			_defaultBusinessNamespace = project.DefaultBusinessNamespace;
			_defaultDataDirectory = project.DefaultBusinessNamespace;
			_defaultBusinessDirectory = project.DefaultBusinessDirectory;
			_defaultSQLDirectory = project.DefaultSQLDirectory;
			_rootDirectory = project.RootDirectory;
			_connectionString = project.ConnectionString;
			_defaultConnectionStringName = project.DefaultConnectionStringName;
			_storedProcedurePrefix = project.StoredProcedurePrefix;
			_autoRunSqlScripts = project.AutoRunSqlScripts;
			_useGeneratedPartialClassFormat = project.UseGeneratedPartialClassFormat;
			_tablePrefixes = new List<string>();
			_tables = new TableCollection();

			_dirty = true;
			_filename = "";
		}

		public Database Database
		{
			get { return _database; }
			set { _database = value; }
		}

		public string DefaultDataNamespace
		{
			get { return _defaultDataNamespace; }
			set
			{
				if ( _defaultDataNamespace != value )
				{
					_defaultDataNamespace = value;
					UpdateState();
				}
			}
		}

		public string DefaultBusinessNamespace
		{
			get { return _defaultBusinessNamespace; }
			set
			{
				if ( _defaultBusinessNamespace != value )
				{
					_defaultBusinessNamespace = value;
					UpdateState();
				}
			}
		}

		public string DefaultDataDirectory
		{
			get { return _defaultDataDirectory; }
			set
			{
				if ( _defaultDataDirectory != value )
				{
					_defaultDataDirectory = value;
					UpdateState();
				}
			}
		}

		public string DefaultBusinessDirectory
		{
			get { return _defaultBusinessDirectory; }
			set
			{
				if ( _defaultBusinessDirectory != value )
				{
					_defaultBusinessDirectory = value;
					UpdateState();
				}
			}
		}

		public string DefaultSQLDirectory
		{
			get { return _defaultSQLDirectory; }
			set
			{
				if ( _defaultSQLDirectory != value )
				{
					_defaultSQLDirectory = value;
					UpdateState();
				}
			}
		}

		public string RootDirectory
		{
			get { return _rootDirectory; }
			set
			{
				if ( _rootDirectory != value )
				{
					_rootDirectory = value;
					UpdateState();
				}
			}
		}

		public string ConnectionString
		{
			get { return _connectionString; }
			set
			{
				if ( _connectionString != value )
				{
					_connectionString = value;
					UpdateState();
				}
			}
		}

		public string DefaultConnectionStringName
		{
			get { return _defaultConnectionStringName; }
			set
			{
				if ( _defaultConnectionStringName != value )
				{
					_defaultConnectionStringName = value;
					UpdateState();
				}
			}
		}

		public string StoredProcedurePrefix
		{
			get { return _storedProcedurePrefix; }
			set
			{
				if ( _storedProcedurePrefix != value )
				{
					_storedProcedurePrefix = value;
					UpdateState();
				}
			}
		}

		public bool AutoRunSqlScripts
		{
			get { return _autoRunSqlScripts; }
			set
			{
				if ( _autoRunSqlScripts != value )
				{
					_autoRunSqlScripts = value;
					UpdateState();
				}
			}
		}

		public bool UseGeneratedPartialClassFormat
		{
			get { return _useGeneratedPartialClassFormat; }
			set
			{
				if ( _useGeneratedPartialClassFormat != value )
				{
					_useGeneratedPartialClassFormat = value;
					UpdateState();
				}
			}
		}

		public List<string> TablePrefixes
		{
			get { return _tablePrefixes; }
		}

		public TableCollection Tables
		{
			get { return _tables; }
		}

		public string Filename
		{
			get { return _filename; }
		}

		public string CreateTableObjectName(string tableName)
		{
			foreach ( string prefix in _tablePrefixes )
			{
				if ( tableName.StartsWith(prefix) )
				{
					return tableName.Substring(prefix.Length - 1);
				}
			}

			tableName = NamingHelper.TitleCase(tableName);

			return tableName;
		}

		public string CreateStoredProcedureName(Method method)
		{
			return _storedProcedurePrefix + method.Table.TableName + "_" + method.MethodName;
		}

		public void LoadSchema()
		{

			UpdateState();
		}

		public void UpdateTablePrefixes(string prefixes)
		{
			string[] prefix = prefixes.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);

			_tablePrefixes.Clear();

			foreach ( string pre in prefix )
			{
				if ( pre.Trim().Length > 0 )
					_tablePrefixes.Add(pre.Trim());
			}

			UpdateState();
		}

		protected void UpdateState()
		{
			_dirty = true;
		}

		#region Save
		public void Save(string filename)
		{
			_filename = filename;
			Save();
		}

		public void Save()
		{
			XmlDocument doc = CreateXmlDoc();

			doc.Save(_filename);
		}

		public string GetXml()
		{
			XmlDocument doc = CreateXmlDoc();

			return doc.ToString();
		}

		protected XmlDocument CreateXmlDoc()
		{
			XmlDocument doc = new XmlDocument();

			XmlElement root = CreateXmlElement(doc);

			doc.AppendChild(root);

			CreateTablePrefixXml(doc, root);

			if ( _database != null )
			{
				_database.SaveXml(doc, root);
			}

			CreateTableXml(doc, root);

			return doc;
		}

		protected XmlElement CreateXmlElement(XmlDocument doc)
		{
			XmlElement xmlEl = doc.CreateElement("Project");

			xmlEl.Attributes.Append(doc.CreateAttribute("DefaultDataNamespace")).Value = _defaultDataNamespace;
			xmlEl.Attributes.Append(doc.CreateAttribute("DefaultBusinessNamespace")).Value = _defaultBusinessNamespace;
			xmlEl.Attributes.Append(doc.CreateAttribute("DefaultDataDirectory")).Value = _defaultDataDirectory;
			xmlEl.Attributes.Append(doc.CreateAttribute("DefaultBusinessDirectory")).Value = _defaultBusinessDirectory;
			xmlEl.Attributes.Append(doc.CreateAttribute("DefaultSQLDirectory")).Value = _defaultSQLDirectory;
			xmlEl.Attributes.Append(doc.CreateAttribute("RootDirectory")).Value = _rootDirectory;
			xmlEl.Attributes.Append(doc.CreateAttribute("ConnectionString")).Value = _connectionString;
			xmlEl.Attributes.Append(doc.CreateAttribute("DefaultConnectionStringName")).Value = _defaultConnectionStringName;
			xmlEl.Attributes.Append(doc.CreateAttribute("StoredProcedurePrefix")).Value = _storedProcedurePrefix;
			xmlEl.Attributes.Append(doc.CreateAttribute("AutoRunSqlScripts")).Value = _autoRunSqlScripts.ToString();
			xmlEl.Attributes.Append(doc.CreateAttribute("UseGeneratedPartialClassFormat")).Value = _useGeneratedPartialClassFormat.ToString();

			return xmlEl;
		}

		protected void CreateTablePrefixXml(XmlDocument doc, XmlElement root)
		{
			XmlElement prefixes = doc.CreateElement("TablePrefixes");

			foreach ( string prefix in _tablePrefixes )
			{
				XmlElement xmlPrefix = doc.CreateElement("Prefix");
				xmlPrefix.Attributes.Append(doc.CreateAttribute("Value")).Value = prefix;

				prefixes.AppendChild(xmlPrefix);
			}

			root.AppendChild(prefixes);
		}

		protected void CreateTableXml(XmlDocument doc, XmlElement root)
		{
			XmlElement xmlTables = doc.CreateElement("Tables");
			root.AppendChild(xmlTables);

			foreach ( Table table in _tables )
			{
				table.SaveXml(doc, xmlTables);
			}
		}
		#endregion

		#region Load
		public void Load(string filename)
		{
			_filename = filename;

			XmlDocument doc = new XmlDocument();

			doc.Load(_filename);

			LoadXml(doc.DocumentElement);
		}

		public void LoadXml(string xml)
		{
			XmlDocument doc = new XmlDocument();

			doc.LoadXml(xml);

			LoadXml(doc.DocumentElement);
		}

		public void LoadXml(XmlElement xmlProject)
		{
			_defaultDataNamespace = xmlProject.GetAttribute("DefaultDataNamespace");
			_defaultBusinessNamespace = xmlProject.GetAttribute("DefaultBusinessNamespace");
			_defaultDataDirectory = xmlProject.GetAttribute("DefaultDataDirectory");
			_defaultBusinessDirectory = xmlProject.GetAttribute("DefaultBusinessDirectory");
			_defaultSQLDirectory = xmlProject.GetAttribute("DefaultSQLDirectory");
			_rootDirectory = xmlProject.GetAttribute("RootDirectory");
			_connectionString = xmlProject.GetAttribute("ConnectionString");
			_defaultConnectionStringName = xmlProject.GetAttribute("DefaultConnectionStringName");
			_storedProcedurePrefix = xmlProject.GetAttribute("StoredProcedurePrefix");

			string temp = xmlProject.GetAttribute("AutoRunSqlScripts");
			bool.TryParse(temp, out _autoRunSqlScripts);

			temp = xmlProject.GetAttribute("UseGeneratedPartialClassFormat");
			bool.TryParse(temp, out _useGeneratedPartialClassFormat);

			XmlNodeList nodes = xmlProject.GetElementsByTagName("Tables");

			if ( nodes.Count > 0 )
			{
				XmlElement xmlTables = nodes[0] as XmlElement;
				foreach ( XmlNode nodeTable in xmlTables.ChildNodes )
				{
					if ( nodeTable.NodeType == XmlNodeType.Element )
					{
						XmlElement xmlTable = nodeTable as XmlElement;
						Table table = new Table(this);
						table.LoadXml(xmlTable);

						_tables.Add(table);
					}
				}
			}

			nodes = xmlProject.GetElementsByTagName("TablePrefixes");

			if ( nodes.Count > 0 )
			{
				XmlElement xmlPrefixes = nodes[0] as XmlElement;

				LoadXmlPrefixes(xmlPrefixes);
			}

			nodes = xmlProject.GetElementsByTagName("Database");
			if ( nodes.Count > 0 )
			{
				_database = new Database();
				_database.LoadXml(nodes[0] as XmlElement);
			}

			DataHelper.LoadResolveFKProperties(_tables);
			DataHelper.ResolveFKIssues(_tables);

			// this must be done after resolve FK issues
			// or else the base table foreign key
			// won't be property hooked up
			DataHelper.ResolveTableIssues(_tables);


		}

		private void LoadXmlPrefixes(XmlElement xmlPrefixes)
		{
			foreach ( XmlNode node in xmlPrefixes.ChildNodes )
			{
				if ( node.NodeType == XmlNodeType.Element )
				{
					XmlElement xmlPrefix = node as XmlElement;

					if ( xmlPrefix.Name == "Prefix" )
					{
						string prefix = xmlPrefixes.GetAttribute("Value");

						_tablePrefixes.Add(prefix);
					}
				}
			}
		}

		#endregion

		public void DeleteTable(Table table)
		{
			if ( _tables.Contains(table) )
			{
				// find all foreign key properties and remove their back references
				table.Properties.OperateOnProperties(delegate(TableProperty tp)
				{
					if ( tp.Type == TablePropertyType.ForeignKey )
					{
						ForeignKeyTableProperty fktp = tp as ForeignKeyTableProperty;

						TableProperty ftp = fktp.ReferenceTable.Properties.FindPropertyByColumnName(fktp.ReferenceColumnName);
						if ( ftp != null && ftp.Type == TablePropertyType.Intrinsic )
						{
							IntrinsicTableProperty itp = ftp as IntrinsicTableProperty;
							itp.ForeignKeys.Remove(fktp);
						}
					}
				}
				);

				// now find any references to this table and convert them to normal
				// intrinsic properties

				table.Properties.OperateOnProperties(delegate(TableProperty tp)
				{
					if ( tp.Type == TablePropertyType.Intrinsic )
					{
						IntrinsicTableProperty itp = tp as IntrinsicTableProperty;

						RemoveReferences(itp);
					}
				}
				);

				_tables.Remove(table);
			}
		}

		protected void RemoveReferences(IntrinsicTableProperty itp)
		{
			if ( itp.ForeignKeys.Count > 0 )
			{
				foreach ( ForeignKeyTableProperty fktp in itp.ForeignKeys )
				{
					// self reference
					if ( fktp.ForeignKeyTable == itp.Table )
						continue;

					IntrinsicTableProperty fitp = fktp.ConvertToIntrinsic();

					TablePropertyCollection tpc = null;

					foreach ( TableProperty stp in fktp.ForeignKeyTable.Properties )
					{
						if ( stp.Type == TablePropertyType.PrimaryKey )
						{
							PrimaryKeyTableProperty spktp = stp as PrimaryKeyTableProperty;

							foreach ( TableProperty sstp in spktp.Properties )
							{
								if ( sstp.Type == TablePropertyType.ForeignKey && sstp == fktp )
								{
									tpc = spktp.Properties;
									break;
								}
							}
						}
						else
						{
							if ( stp.Type == TablePropertyType.ForeignKey && stp == fktp )
							{
								tpc = fktp.ForeignKeyTable.Properties;
							}
						}

						if ( tpc != null ) break;
					}

					tpc.Insert(tpc.IndexOf(fktp), fitp);
					tpc.Remove(fktp);
				}
			}
		}

		public void GenerateCode()
		{
			GenerateCode(null);
		}

		public void GenerateCode(BackgroundWorker worker)
		{
			int total = _tables.Count * 4;
			int current = 0;
			foreach ( Table table in _tables )
			{
				BusinessObjectGenerator bog = new BusinessObjectGenerator(table);
				bog.CreateBusinessObject();

				current++;
				if ( worker != null )
					worker.ReportProgress((int)( ( (float)current / (float)total ) * 100 ));

				DbObjectGenerator dbog = new DbObjectGenerator(table);
				dbog.CreateDbObject();

				current++;
				if ( worker != null )
					worker.ReportProgress((int)( ( (float)current / (float)total ) * 100 ));

				PrimaryKeyGenerator pkg = new PrimaryKeyGenerator(table);
				pkg.CreatePrimaryKey();

				current++;
				if ( worker != null )
					worker.ReportProgress((int)( ( (float)current / (float)total ) * 100 ));

				StoredProcedureGenerator spg = new StoredProcedureGenerator(table);
				spg.CreateStoredProcedures();

				current++;
				if ( worker != null )
					worker.ReportProgress((int)( ( (float)current / (float)total ) * 100 ));
			}
		}
	}
}
